// Copyright Leonid Shlyapnikov 2010
// Distributed under the GNU Lesser General Public License http://www.gnu.org/licenses/lgpl.html
//    author: Leonid Shlyapnikov, created on: Feb 1, 2010

#include <boost/exception/all.hpp>
#include <boost/test/test_tools.hpp>
#include <boost/cstdint.hpp>
#include <exception>
#include <string>

#include "../../main/fixparser/MemoryArea.h"
#include "../../main/fixparser/RawData.h"
#include "../../main/fixparser/Field.h"
#include "../../main/fixparser/log.h"


void MemoryArea_allocate() {
    using fixparser::MemoryArea;

    // GIVEN
    MemoryArea ma(10);
    BOOST_CHECK_EQUAL(ma.capacity(), 10);
    BOOST_CHECK_EQUAL(ma.size(), 0);

    // WHEN
    void* bytes = ma.allocate(5);

    // THEN
    BOOST_CHECK_EQUAL(ma.capacity(), 10);
    BOOST_CHECK_EQUAL(ma.size(), 5);
    BOOST_CHECK_NE(reinterpret_cast<int64_t>(bytes), 0);
    BOOST_CHECK(ma.contains(bytes));
}

void MemoryArea_assert_contains() {
    using fixparser::MemoryArea;

    // GIVEN
    MemoryArea ma1(128);
    MemoryArea ma2(128);

    // WHEN
    void* bytes1 = ma1.allocate(5);
    void* bytes2 = ma2.allocate(5);

    // THEN
    BOOST_CHECK_EQUAL(ma1.contains(bytes1), true);
    BOOST_CHECK_EQUAL(ma1.contains(bytes2), false);
    BOOST_CHECK_EQUAL(ma2.contains(bytes2), true);
    BOOST_CHECK_EQUAL(ma2.contains(bytes1), false);
}

void MemoryArea_allocateShouldReturnZero() {
    using fixparser::MemoryArea;
    using fixparser::MemoryAreaBadAlloc;

    // GIVEN
    MemoryArea ma(10);
    BOOST_CHECK_EQUAL(ma.capacity(), 10);
    BOOST_CHECK_EQUAL(ma.size(), 0);

    // WHEN-1
    LOG_INFO("THE NEXT ERROR LOG ENTRY IS OK, it is just a test for std::bad_alloc");
    BOOST_CHECK_THROW(ma.allocate(15), std::bad_alloc);

    // THEN-1
    BOOST_CHECK_EQUAL(ma.capacity(), 10);
    BOOST_CHECK_EQUAL(ma.size(), 0);

    // WHEN-2
    std::string exceptionMsg;
    try {
        ma.allocate(15);
    } catch (const MemoryAreaBadAlloc& e) {
        exceptionMsg = e.what();
    }

    // THEN-2
    BOOST_CHECK_MESSAGE(exceptionMsg.size() > 0, exceptionMsg);
    BOOST_CHECK_MESSAGE(exceptionMsg.find("Not enough capacity") == 0, exceptionMsg);
}

void MemoryArea_multipleAllocates() {
    using fixparser::MemoryArea;

    // GIVEN
    MemoryArea ma(10);
    BOOST_CHECK_EQUAL(ma.capacity(), 10);
    BOOST_CHECK_EQUAL(ma.size(), 0);

    // WHEN
    void* bytes0 = ma.allocate(5);
    void* bytes1 = ma.allocate(5);
    LOG_INFO("THE NEXT ERROR LOG ENTRY IS OK, it is just a test for std::bad_alloc");
    BOOST_CHECK_THROW(ma.allocate(1), std::bad_alloc);

    // THEN
    BOOST_CHECK_EQUAL(ma.capacity(), 10);
    BOOST_CHECK_EQUAL(ma.size(), 10);
    BOOST_CHECK_NE(reinterpret_cast<int64_t>(bytes0), 0);
    BOOST_CHECK_NE(reinterpret_cast<int64_t>(bytes1), 0);
    BOOST_CHECK_EQUAL(reinterpret_cast<int64_t>(bytes1), reinterpret_cast<int64_t>(bytes0) + 5);
    BOOST_CHECK(ma.contains(bytes0));
    BOOST_CHECK(ma.contains(bytes1));
}

void MemoryArea_allocateRawData() {
    using fixparser::MemoryArea;
    using fixparser::RawData;

    // GIVEN
    MemoryArea ma(RawData::DEFAULT_CAPACITY * 2);

    // WHEN
    RawData* rawData = ma.create<RawData>();

    // THEN
    BOOST_CHECK_NE(reinterpret_cast<int64_t>(rawData), 0);
    size_t expectedSize = (RawData::DEFAULT_CAPACITY) + sizeof(RawData);
    BOOST_CHECK_EQUAL(ma.size(), expectedSize);
    BOOST_CHECK(ma.contains(rawData));
}

void MemoryArea_allocateShowThrowInvalidArgument() {
    using fixparser::MemoryArea;

    // GIVEN
    MemoryArea ma(10);

    // WHEN
    BOOST_CHECK_THROW(ma.allocate(0), std::invalid_argument);

    // THEN
    BOOST_CHECK_EQUAL(ma.capacity(), 10);
    BOOST_CHECK_EQUAL(ma.size(), 0);
}

void MemoryArea_createField() {
    using fixparser::MemoryArea;
    using fixparser::Field;

    // GIVEN
    MemoryArea ma(sizeof(Field) + 10);

    // WHEN
    Field* f = ma.create<Field>();

    // THEN
    BOOST_CHECK_NE(f, reinterpret_cast<void*>(0));
    BOOST_CHECK_EQUAL(ma.size(), sizeof(Field));
    BOOST_CHECK(ma.contains(f));
}

void MemoryArea_createFieldShouldThrowBadAlloc() {
    using fixparser::MemoryArea;
    using fixparser::Field;

    const size_t capacity = sizeof(Field) - 1;

    // GIVEN
    MemoryArea ma(capacity);

    // WHEN
    LOG_INFO("THE NEXT ERROR LOG ENTRY IS OK, it is just a test for std::bad_alloc");
    BOOST_CHECK_THROW(ma.create<Field>(), std::bad_alloc);

    // THEN
    BOOST_CHECK_EQUAL(ma.size(), 0);
}

void MemoryArea_reset() {
    using fixparser::MemoryArea;
    using fixparser::Field;

    // GIVEN
    MemoryArea ma(sizeof(Field) + 10);
    Field* f = ma.create<Field>();
    BOOST_CHECK_NE(f, reinterpret_cast<void*>(0));
    BOOST_CHECK_EQUAL(ma.size(), sizeof(Field));

    // WHEN
    ma.reset();

    // THEN
    BOOST_CHECK_EQUAL(ma.size(), 0);
    BOOST_CHECK(false == ma.contains(f));  // MemoryArea has been reset, so the pointer is not valid anymore
}

//BOOST_AUTO_TEST_CASE( MemoryAreaConstructorShouldThrowInvalidArgument ) {
//    using fixparser::MemoryArea;
////    BOOST_CHECK_THROW(MemoryArea(-1), std::invalid_argument);
//    BOOST_CHECK_THROW(MemoryArea(-1), std::bad_alloc);
//}
